#version 130
#extension GL_EXT_texture_array : enable

uniform sampler2D noiseTexture;
uniform sampler2DArray textureArray;
uniform sampler2DArray normalArray;
uniform sampler2DArray detailArray;

uniform vec3 playerPos;
uniform vec3 camLookAt;
uniform vec3 sunPosition;
uniform float blockAlpha;

// Vertex shader input
in vec3 fWorldPos;
in vec3 fNormal;
in vec3 fMaterial;
in vec3 fAlpha;
in float fAmbientOcclusion;
in float fIntensity;
in float fDistance;
in float pickDistance;

const float textureScale = 0.125f;  // 0.095 good, 0.125 old
const float detailScale = 0.9f;
float PI = 3.14159265358979323846264;

/////////////////
// returns detail and detail normal
/////////////////
void getDetail(sampler2DArray texture, float index, vec3 coord, float scale, vec3 weight, vec3 weight2, out float detailColor, out vec3 normal)
{
	float wXY = weight.z;
	float wXZ = weight.y;
	float wYZ = weight.x;
	
	vec2 coordXY = coord.xy * scale;
	vec2 coordXZ = coord.xz * scale;
	vec2 coordYZ = coord.yz * scale;
	
	vec4 colorXY = texture2DArray(texture, vec3(coordXY.xy, index));
	vec4 colorXZ = texture2DArray(texture, vec3(coordXZ.xy, index));
	vec4 colorYZ = texture2DArray(texture, vec3(coordYZ.xy, index));
	
	detailColor = colorXY.a * wXY + colorXZ.a * wXZ + colorYZ.a * wYZ;

	vec3 bumpNormal1 = (colorXY.r - 0.5) * vec3(1, 0, 0) + (colorXY.g - 0.5) * vec3(0, 1, 0) + (colorXY.b - 0.5) * vec3(0, 0, 1);
	vec3 bumpNormal2 = (colorXZ.r - 0.5) * vec3(0, 0, 1) + (colorXZ.g - 0.5) * vec3(1, 0, 0) + (colorXZ.b - 0.5) * vec3(0, 1, 0);
	vec3 bumpNormal3 = (colorYZ.r - 0.5) * vec3(0, 1, 0) + (colorYZ.g - 0.5) * vec3(0, 0, 1) + (colorYZ.b - 0.5) * vec3(1, 0, 0);	
	
	normal = bumpNormal1 * weight2.z + bumpNormal2 * weight2.y + bumpNormal3 * weight2.x;	
}

float getAlphaWeight(float alpha, float noise)
{	
	alpha = smoothstep(0, 0.9, alpha + noise - 0.45);
	return pow(alpha, 3);
}

vec3 getFragmentNormal(float index, float weightXY, float weightXZ, float weightYZ)
{
	vec2 coordXY = fWorldPos.xy * textureScale;
	vec2 coordXZ = fWorldPos.xz * textureScale;	
	vec2 coordYZ = fWorldPos.yz * textureScale;	

	vec4 bump1 = texture2DArray(normalArray, vec3(coordXY.xy, index));	
	vec4 bump2 = texture2DArray(normalArray, vec3(coordXZ.xy, index));
	vec4 bump3 = texture2DArray(normalArray, vec3(coordYZ.xy, index));

	vec3 bumpNormal1 = (bump1.r - 0.5) * vec3(1, 0, 0) + (bump1.g - 0.5) * vec3(0, 1, 0) + (bump1.b - 0.5) * vec3(0, 0, 1);
	vec3 bumpNormal2 = (bump2.r - 0.5) * vec3(0, 0, 1) + (bump2.g - 0.5) * vec3(1, 0, 0) + (bump2.b - 0.5) * vec3(0, 1, 0);
	vec3 bumpNormal3 = (bump3.r - 0.5) * vec3(0, 1, 0) + (bump3.g - 0.5) * vec3(0, 0, 1) + (bump3.b - 0.5) * vec3(1, 0, 0);
	
	return normalize(weightXY * bumpNormal1 + weightXZ * bumpNormal2 + weightYZ * bumpNormal3);
}

vec4 sampleWeighed(sampler2DArray texture, float index, vec3 coord, vec3 weight, float scale)
{
	float wXY = weight.z;
	float wXZ = weight.y;
	float wYZ = weight.x;

	vec2 coordXY = coord.xy * scale;
	vec2 coordXZ = coord.xz * scale;
	vec2 coordYZ = coord.yz * scale;
	
	vec4 colorXY = texture2DArray(texture, vec3(coordXY.xy, index));
	vec4 colorXZ = texture2DArray(texture, vec3(coordXZ.xy, index));
	vec4 colorYZ = texture2DArray(texture, vec3(coordYZ.xy, index));
	
	return colorXY * wXY + colorXZ * wXZ + colorYZ * wYZ;
}

vec4 sampleWeighed2D(sampler2D texture, vec3 coord, vec3 weight, float scale)
{
	float wXY = weight.z;
	float wXZ = weight.y;
	float wYZ = weight.x;

	vec2 coordXY = coord.xy * scale;
	vec2 coordXZ = coord.xz * scale;
	vec2 coordYZ = coord.yz * scale;
	
	vec4 colorXY = texture2D(texture, coordXY.xy);
	vec4 colorXZ = texture2D(texture, coordXZ.xy);
	vec4 colorYZ = texture2D(texture, coordYZ.xy);
	
	return colorXY * wXY + colorXZ * wXZ + colorYZ * wYZ;
}

vec4 desaturate(vec4 color)
{
	float lum = (0.299 * color.r) + (0.587 * color.g) + (0.114 * color.b);
	return vec4(lum, lum, lum, 1.0);
}

void main()
{
	float mXY, mXZ, mYZ; 
	float sXY, sXZ, sYZ;

	// Raise each component of normal vector to 4th power.
	//vec3 blend = abs(normalize(fNormal));
	vec3 blend = clamp(abs(normalize(fNormal)) - 0.5,0.0,1.0);	
	blend *= blend;
	blend *= blend;
		
	// Normalize result by dividing by the sum of its components.
	blend /= dot(blend, vec3(1.0, 1.0, 1.0));
	
	mXY = blend.z;
	mXZ = blend.y;
	mYZ = blend.x;
	
	sXY = normalize(fNormal).z;
	sXZ = normalize(fNormal).y;
	sYZ = normalize(fNormal).x;
	
	/* vec2 coordXY = fWorldPos.xy * textureScale;
	vec2 coordXZ = fWorldPos.xz * textureScale;	
	vec2 coordYZ = fWorldPos.yz * textureScale;	 */
	
	vec4 blendedColor = sampleWeighed(textureArray, fMaterial.r, fWorldPos, blend, textureScale);
	vec4 blendedColor2 = sampleWeighed(textureArray, fMaterial.g, fWorldPos, blend, textureScale);
	vec4 blendedColor3 = sampleWeighed(textureArray, fMaterial.b, fWorldPos, blend, textureScale);

	//vec4 d_blendedColor = sampleWeighed(textureArray, fMaterial.r, fWorldPos, blend, 0.5);
	//vec4 d_blendedColor2 = sampleWeighed(textureArray, fMaterial.g, fWorldPos, blend, 0.5);
	//vec4 d_blendedColor3 = sampleWeighed(textureArray, fMaterial.b, fWorldPos, blend, 0.5); 

/*
	blendedColor.rgb *= desaturate(d_blendedColor).rgb;
	blendedColor2.rgb *= desaturate(d_blendedColor2).rgb; 
	blendedColor3.rgb *= desaturate(d_blendedColor3).rgb;   */
	
	 
	// Blending of 2 fake materials (for now)
	vec3 alph = fAlpha.rgb;
	
	float noise = 0.0;
	float s = 0.4;
	noise += mXY * texture2D(noiseTexture, fWorldPos.xy * s).r;
	noise += mXZ * texture2D(noiseTexture, fWorldPos.xz * s).r;
	noise += mYZ * texture2D(noiseTexture, fWorldPos.yz * s).r;
	
	alph = fAlpha.rgb;
	alph.r = getAlphaWeight(alph.r, noise);	
	alph.g = getAlphaWeight(alph.g, noise);	
	alph.b = getAlphaWeight(alph.b, noise);	
	
	alph /= (alph.r + alph.g + alph.b);
				
	vec3 finalColor;
	
	finalColor += alph.r * blendedColor.rgb;
	finalColor += alph.g * blendedColor2.rgb;	
	finalColor += alph.b * blendedColor3.rgb; 
	
	vec3 detailNormal1, detailNormal2, detailNormal3;
	float detailColor1, detailColor2, detailColor3;
	getDetail(detailArray, fMaterial.r, fWorldPos, detailScale, blend, fNormal, detailColor1, 
		detailNormal1);
	getDetail(detailArray, fMaterial.g, fWorldPos, detailScale, blend, fNormal, detailColor2, 
		detailNormal2);
	getDetail(detailArray, fMaterial.b, fWorldPos, detailScale, blend, fNormal, detailColor3, 
		detailNormal3);
		
	float detailColor = detailColor1 * alph.r + detailColor2 * alph.g + detailColor3 * alph.b;
	vec3 detailNormal = detailNormal1 * alph.r + detailNormal2 * alph.g + detailNormal3 * alph.b;
	
	//finalColor = detailColor.rgb;
	finalColor *= detailColor * 2;
	//finalColor = (finalColor * 0.00001) + detailColor;
	//finalColor /= 1.6;

	//----- Basic light in absence of openGL lighting, looks weird and ugly 
	//----- but at least highlights some parts of terrain
	vec3 norm, lightDir;
	vec4 diffuse, ambient, globalAmbient;
	float NdotL;	// modified normal dot light dir
	float ONdotL;	// original normal dot light dir

	lightDir = normalize(sunPosition - fWorldPos);
	// calculate the original normal vs sun, to not light surfaces facing away from the sun
	ONdotL = max(dot(fNormal, lightDir),0);	// puts ONdotL between 0..1
	ONdotL = min(1, ONdotL+0.5);	// should put ONdotL at either 0 or 1
	
	vec3 fragDetailNormal = getFragmentNormal(fMaterial.r, sXY, sXZ, sYZ) * alph.r +
		                    getFragmentNormal(fMaterial.g, sXY, sXZ, sYZ) * alph.g +
		                    getFragmentNormal(fMaterial.b, sXY, sXZ, sYZ) * alph.b;
	
	//vec3 fragNormal = normalize(fragDetailNormal); 
	vec3 fragNormal = mix(fNormal.xyz, normalize(fragDetailNormal), ONdotL);	
	detailNormal.rgb *= vec3(1, 1, 1);
	
	//vec3 fragNormal = normalize(fragDetailNormal) + normalize(detailNormal);
	//vec3 fragNormal = normalize(detailNormal);
	//vec3 fragNormal = fNormal.xyz;
	fragNormal += detailNormal * 2;
	fragNormal = normalize(fragNormal);	
		
	vec4 light = vec4(0,0,0,1);	
	vec4 playerLight = vec4(0, 0, 0, 1);
	
	NdotL = max(dot(fragNormal, lightDir), 0.0);	
	
	diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;	  // change to uniform array?
	vec4 dayAmbient = vec4(0.4, 0.4, 0.4, 1.0);
	vec4 nightAmbient = vec4(0.25, 0.25, 0.25, 1.0);
	ambient = mix(nightAmbient, dayAmbient, fIntensity);
		
	float ao = clamp(1.0 - (fAmbientOcclusion/250.0), 0.0, 1.0);
	
	// I multiply ambient occlusion factor, which has the effect of keeping completly
	// dark areas dark, while making slightly occluded areas a bit brighter (otherwise
	// it resulted in outside terrain being black sometimes). Not sure how good is this
	// fix but unless we have a way to keep caves 100% dark while allowing surface 
	// terrain to stay only a bit shaded I will keep it.
	light = fIntensity * (ao * ((NdotL * diffuse) + ambient));
	
	// Some lame attenuation computing, correct me whenever you want, but works as a 
	// simple simulation of player "torch", so we can see in completly dark places	
	float att = 0.0; // = 1.0 / (0.1f + (0.01f * fDistance) + (0.02f * fDistance * fDistance));
	
	// FLASHLIGHT
	const float SPOT_EXPONENT = 20.0;
	const float SPOT_CONST_ATT = 0.3;
	const float SPOT_LIN_ATT = 0.05;
	const float SPOT_QUAD_ATT = 0.001;
		
	vec3 flashDir = normalize(camLookAt);
	vec3 lDir = normalize(fWorldPos - playerPos);
	
	float spotEffect = clamp(dot(flashDir, lDir), 0, 1);

	if (spotEffect > 0.0) 
	{	
		spotEffect = pow(spotEffect, SPOT_EXPONENT);
		 
		float lambertTerm = clamp(dot(normalize(fragNormal), -flashDir), 0.3, 0.6);
		
		att = spotEffect / (SPOT_CONST_ATT +
				    	      SPOT_LIN_ATT * fDistance +
							 SPOT_QUAD_ATT * fDistance * fDistance) * lambertTerm;
	}		

	att = clamp(att, 0.0, 1.0);	
	// Need to multiply att by some colour because otherwise if its completly dark
	// light will be close to 0 and doing 0 * att won't give us any more light
	//light += vec4(1, 1, 1, 1.0) * att;

	finalColor.rgb *= light.rgb;
	
    gl_FragColor = vec4(finalColor.rgb, 1);
}
